NATについて学んでみる + nginx-lua改造で簡単にNAT越えやってみる
概要
やってみたくなった。
改造、という言葉がある時点で泥沼になるのは予想できるよね?
NAT
いろいろ。ここの解説がわかりやすかった。
https://plaza.rakuten.co.jp/hisuirai/diary/201211080000/
http://www.think-like-a-computer.com/2011/09/16/types-of-nat/
Cone
・full cone NAT
・address restricted cone NAT
・port restricted cone NAT
Symmetric
・sequential port symmetric NAT
・random port symmetric NAT
要件
nginx luaを介して情報交換し、クライアント間でP2Pできるようにする。ようは、nginx luaでシグナリングを行う。
かなったこと
クライアントA,Bに対して、A,Bがサーバに送付したudpパケットを元に、nginx streamを介してシグナリングを行い、A-B間でP2Pができるようにする。
TURN機構は含まないため、symmetric natは突破というか、解決しない。
実証実験環境
udpを受け付けるサーバを立てて、そこにクライアントからudpを送る ->
サーバ側から状態を観測、変換済み/到達可能なip:portをクライアントに返す ->
クライアントはws接続時にそのパラメータを送ってきて、ws接続寿命の間はudpでのデータ送信ができる、という状態になる
面白い話:モバイルルータとNAT越え
http://custardfilleddonut.blog.fc2.com/blog-entry-19.html
mobileRouterによっては、公式にSymmetricNATを使っていて、NAT越えしづらいのが把握できる、という感じの話がある。
プレイヤー間p2pなどで、サーバ以外の2者をつなぐのがしんどい。turn必須になっちゃう。
現在までの理解:
・やりたいことは、サーバからudpを使ったpushをクライアントへと送ること。
・クライアント側はNATの向こう側にいることが多いので、NATを越えられる状態が必要になる。
・サーバにむけてudpを打てれば、サーバからudpを送り返すことはできるようになる(情報が揃う
full corn -> そのまま送り返せばOK(ごく稀に存在。
address restricted -> 送信先からの返信ならばそのままでOK、そうでない場合、双方向にパケットを打って経路を構築する必要がある。
port restricted -> 受信側 = サーバでポートを観測できてればOK
想定できる安価なケース
1.nginxでudp通信を受ける
2.ip、portをユーザー単位で保存する
3.なんらかの方法でnginx_luaでのws設立タイミングでその情報に触れる(ユーザー単位での情報のすくい上げを行う
4.nginx_luaからudpを発信する際にその情報を使う(接続の再セットを行う際は、ws経由でサーバに送って云々になる。
1~3で、同じnginxが使われれば最高という感じ。
udpサーバは、ユーザーid+tokenのような情報を受け取って、ipとportを保存する。
これだけでOKなはず。
full: クライアントからのudp送付元がそのままudpを返せるのでOK
address restricted: クライアントからのudp送付元がそのままudpを返せるのでOK
port restricted cone NAT:
もっと安価なケース
nginx stream moduleを使う。
1.3以降だと入れられそうで、あとはなんというか、Dockerで動かしてlistenができればいいのか。
-> 対応ポートに/udpつけたらudpに対応した。これで、Dockerでnginx stream udpの受付ができた。
で、
アプリケーションを動かして、udpを受け取り、そのaddrとportを覚えておく必要がある。
まずはnginx-luaのstreamモジュールでやってみるか、、あれでudpを受け取って辞書が使えればベスト。うーん、この構造はダメっぽいかな。
・そもそもwsと共存できてる?
・udpでのデータを受け取って、nginx_luaから触れるようにするにはどうするか:idをパラメータにしてしまうか。それともudpでレスポンスを返すようにするか。
レスポンスそのまま返せると思うんだよな。
-> 返せた。で、LAN環境だと返答も受け取ることができた。
で、あとは、AWSにのっけてテスト。
-> 今回はstreamを使ってudpのserver/senderを立ち上げておき、そこを経由してデータを流すことで実現した。
特定のポートからudpを射出する装置 みたいなのがあればいいのか よさそう。それが特定のポートをlistenしてるソケットである必要性がある?
ありそう。
nginx unit goでそのまま起動できるudp 反射機を用意して、そこにip + ポートで送出させればよさそう。
その機構とnginx luaとのコミュニケーションはどうやるのがいいんだろう。udp?まあそれでも良さそう。
こうすると、udpポートを1つしか消費せず、データを流せる。
事前にws -> キーを返す -> キーをudpサーバに送る -> udpサーバでip:port:キーを保持、
nginx luaからudpをキーで送出 -> リフレクタがキーからデータをip:portへと送付
うーーん大変そう。でもudpだから平気かな。どうせ送り出すのは変わらないわけで。udpで送り出せばいいか。
この部分を組むか。それでudp沼はおしまい。
udp multiplexを勉強してみよう。
-> 結果的に、nginx luaのudp送出でポートを指定できればそれで良さそう。
nginx luaを改造するためにコードを読む
このprでtcp bindが提供されてるんだけど、こんな感じでudpのポートを指定するやつを書ければいいんだと思う。
https://github.com/openresty/lua-nginx-module/pull/712/files
・定義
・cの関数を書く
あたりが必要。
nginx luaを書き換えつつコンパイルすればいいのだろうと思って動かしてみる。
nginx luaのcosocketを書き換えてみる。
-> socketaddrっていうstructがあって、そいつが宛先の情報を持っているんだけど、送信時に送信ポートを指定する手段がなさそう。
これは何というか、ipv4とか6とは切り離されたパラメータだからっぽい。
luaのudpソケットについて調べる。
これsendとreceiveが一致してるような感じなんだよな。というか送り元はポート指定できない的な。
送り先のポートは指定できるんだけど、送り元のポート指定ができない。
これが指定できれば苦労しないんだよな多分。
-> 送り元のポート指定は無理っぽい。sockaddr structになってしまっていて、多分もともとできない。
で、残った手としては、通信を受けるのに使っているポートを割り出して、そのパラメータをクライアントへと伝え、クライアントから通信させる。
残念な点としては、udpの受け口がとても広範囲に必要なこと。ただしポートを消費しない。経路確立してしまえばそのまま送り出せる。
送り元のポートは、connectionそれ自体のポートを使用している。こうすることで、ポート消費を抑えている。
おまけ
ネットワーク帯域を計測する
https://qiita.com/takish/items/3a9ea74cfec4683ddf56
Lua udp sock
https://github.com/openresty/lua-nginx-module/blob/master/t/087-udp-socket.t
クラウドフレアの記事 大量通信を低レイテンシでみたいな話。
https://blog.cloudflare.com/how-to-achieve-low-latency/
ファイルディスクリプタについて
プロセスや実行ファイルにとって外部の資源にアクセスしたりアクセスされたりする際に使用される抽象的なインターフェース
-> nginxだと、一つのワーカーごとに開けるコネクションの数になっていたはず。workerに対して保持できるfdの数を増やして対応していた。
Pacingの話
http://www.aist.go.jp/aist_j/press_release/pr2005/pr20050606/pr20050606.html